#define _USE_MATH_DEFINES
#define NOMINMAX

#include <limits>
#include <cctype>
#include <string>

#ifdef WIN32
#define NOMINMAX
#include <windows.h>
#endif

#include <sh/sh.hpp>
#include <CG/cg.h>
#include <CG/cgGL.h>
#include <iostream>
#include <fstream>
#include <math.h>
#include "fileIO.h"
#include "glm.h"
#include "Constants.h"
#include "Vector.h"
#include "transformation.h"
#include "GL_Extensions.h"
#include "distance.h"

using namespace std;
using namespace SH;

#define T(x) (model->triangles[(x)])


static void checkForCgError(const char *situation)
{
	CGerror error;
	const char *string = cgGetLastErrorString(&error);

	if (error != CG_NO_ERROR) {
		printf("%s: %s: %s\n",
			"Main.cpp", situation, string);
		if (error == CG_COMPILER_ERROR) {
			printf("%s\n", cgGetLastListing(myCgContext));
		}
		exit(1);
	}
}


void drawTile( float a, float b, float c, float d,  float ta, float tb, float tc, float td, float y )
{
	glBegin(GL_QUADS); 
	glNormal3f(0.0, 1.0, 0.0);
	glMultiTexCoord3fARB(GL_TEXTURE0 + 1, 1.0, 0.0, 0.0); // tangents
	glMultiTexCoord3fARB(GL_TEXTURE0 + 2, 0.0, 0.0, 1.0); // binormals

	glTexCoord2f(tc, td);
	glVertex3f(c, y, d);

	glTexCoord2f(ta, td);
	glVertex3f(a, y, d);

	glTexCoord2f(ta, tb);
	glVertex3f(a, y, b);

	glTexCoord2f(tc, tb);
	glVertex3f(c, y, b);

	glEnd();
}


void recursiveDrawTile( float a, float b, float c, float d, 
					   float ta, float tb, float tc, float td, int level, float y )
{
	if( level == 1)
		drawTile(a, b, c, d, ta, tb, tc, td, y);

	else
	{
		recursiveDrawTile( (a+c)/2, (b+d)/2, c, d, 1, 1, 0, 0, level-1, y );
		recursiveDrawTile( a, (b+d)/2, (a+c)/2, d, 1, 1, 0, 0, level-1, y );
		recursiveDrawTile( a, b, (a+c)/2, (b+d)/2, 1, 1, 0, 0, level-1, y );
		recursiveDrawTile( (a+c)/2, b, c, (b+d)/2, 1, 1, 0, 0, level-1, y );
	}
}


void drawSide(float x1, float y1, float z1,
			  float x2, float y2, float z2,
			  float x3, float y3, float z3,
			  float x4, float y4, float z4, 

			  float n1, float n2, float n3,
			  float t1, float t2, float t3,

			  float tx1, float ty1,
			  float tx2, float ty2,
			  float tx3, float ty3,
			  float tx4, float ty4
			  )
{
	glBegin(GL_QUADS); 
	glNormal3f(n1, n2, n3);
	glMultiTexCoord3fARB(GL_TEXTURE0 + 1, t1, t2, t3); // tangents
	glMultiTexCoord3fARB(GL_TEXTURE0 + 2, t2*n3-n2*t3, -(t1*n3-t3*n1), t1*n2-t2*n1); // binormals   txn

	glTexCoord2f(tx1, ty1);
	glVertex3f(x1, y1, z1);

	glTexCoord2f(tx2, ty2);
	glVertex3f(x2, y2, z2);

	glTexCoord2f(tx3, ty3);
	glVertex3f(x3, y3, z3);

	glTexCoord2f(tx4, ty4);
	glVertex3f(x4, y4, z4);

	glEnd();
}


void drawCuboid(float startx,float starty, float startz, float factor)
{
	drawSide(  startx-factor, starty+factor, startz+factor,		// up 
		startx-factor, starty+factor, startz-factor,
		startx+factor, starty+factor, startz-factor,
		startx+factor, starty+factor, startz+factor,

		0, 1.0, 0.0,		// normal 
		0.0, 0.0, -1.0,	// tangent

		0.0, 0.0,
		1.0, 0.0,
		1.0, 1.0,
		0.0, 1.0);

	drawSide(  startx-factor, starty-factor, startz-factor,	// down
		startx-factor, starty-factor, startz+factor,
		startx+factor, starty-factor, startz+factor,
		startx+factor, starty-factor, startz-factor,

		0, -1.0, 0.0,
		0.0, 0.0, 1.0,

		0.0, 0.0,
		1.0, 0.0,
		1.0, 1.0,
		0.0, 1.0);

	drawSide(  startx-factor, starty-factor, startz+factor,		//front
		startx-factor, starty+factor, startz+factor,
		startx+factor, starty+factor, startz+factor,
		startx+factor, starty-factor, startz+factor,

		0, 0.0, 1.0,
		0.0, 1.0, 0.0,

		0.0, 0.0,
		1.0, 0.0,
		1.0, 1.0,
		0.0, 1.0);


	drawSide( startx-factor, starty+factor, startz-factor,		//back
		startx-factor, starty-factor, startz-factor,
		startx+factor, starty-factor, startz-factor,
		startx+factor, starty+factor, startz-factor,

		0, 0.0, -1.0,
		0.0, -1.0, 0.0,

		0.0, 0.0,
		1.0, 0.0,
		1.0, 1.0,
		0.0, 1.0);


	drawSide(  startx+factor, starty-factor, startz+factor,		// right
		startx+factor, starty+factor, startz+factor,
		startx+factor, starty+factor, startz-factor,
		startx+factor, starty-factor, startz-factor,

		1.0, 0.0, 0.0,
		0.0, 1.0, 0.0,

		0.0, 0.0,
		1.0, 0.0,
		1.0, 1.0,
		0.0, 1.0);

	drawSide(  startx-factor, starty-factor, startz-factor,		// left 
		startx-factor, starty+factor, startz-factor,
		startx-factor, starty+factor, startz+factor,
		startx-factor, starty-factor, startz+factor,

		-1.0, 0.0, 0.0,
		0.0, 1.0, 0.0,

		0.0, 0.0,
		1.0, 0.0,
		1.0, 1.0,
		0.0, 1.0);

}



void drawRightSide(int sWidth, int endWidth, int sLength, int endLength, int size1, int size2, float startx = -200)
{
	float startz, starty;

	for( int i=sWidth; i<endWidth; i++)
		for( int j=sLength; j<endLength; j++)
		{
			starty = i*size1; startz = j*size2;
			float endy = (i+1)*size1, endz = (j+1)*size2;

			drawSide(   startx, starty, endz,		// right
				startx, endy, endz,
				startx, endy, startz,
				startx, starty, startz,

				1.0, 0.0, 0.0,
				0.0, 1.0, 0.0,

				0.0, 0.0,
				1.0, 0.0,
				1.0, 1.0,
				0.0, 1.0);
		}
}



void drawLeftSide(int sWidth, int endWidth, int sLength, int endLength, int size1, int size2, float startx = 200)
{
	float startz, starty;

	for( int i=sWidth; i<endWidth; i++)
		for( int j=sLength; j<endLength; j++)
		{
			starty = i*size1; startz = j*size2;
			float endy = (i+1)*size1, endz = (j+1)*size2;

			drawSide(   startx, starty, startz,		// left
				startx, endy, startz,
				startx, endy, endz,
				startx, starty, endz,

				-1.0, 0.0, 0.0,
				0.0, 1.0, 0.0,

				0.0, 0.0,
				1.0, 0.0,
				1.0, 1.0,
				0.0, 1.0);

		}
}

void drawFrontSide(int sWidth, int endWidth, int sLength, int endLength, int size1, int size2, float startz = 200)
{
	float startx, starty;

	for( int i=sWidth; i<endWidth; i++)
		for( int j=sLength; j<endLength; j++)
		{
			startx = i*size1; starty = j*size2;
			float endx = (i+1)*size1, endy = (j+1)*size2;

			drawSide(   startx, starty, startz,		// front
				startx, endy, startz,
				endx, endy, startz,
				endx, starty, startz,

				0, 0.0, 1.0,
				0.0, 1.0, 0.0,

				0.0, 0.0,
				1.0, 0.0,
				1.0, 1.0,
				0.0, 1.0);

		}
}
void minMipmapDistanceMap(float* myDispmap, float* myDispmapLower, int w, int h, int d, int resolution)
{
	float minValue;

	for (int z = 0; z < d; z+=resolution) 
	{
		for (int y = 0; y < h; y+=resolution) 
		{
			for (int x = 0; x < w; x+=resolution) 
			{
				minValue = 100;

				for(int kk=0; kk<resolution; kk++)
					for(int jj=0; jj<resolution; jj++)
						for(int ii=0; ii<resolution; ii++)
						{
							if( myDispmap[(z+kk)*w*h + (y+jj)*w + (x+ii)] < minValue )
								minValue = myDispmap[(z+kk)*w*h + (y+jj)*w + (x+ii)];
						}
						myDispmapLower[ (z/resolution)*(w/resolution)*(h/resolution) + (y/resolution)*(w/resolution) + (x/resolution) ] = minValue;
			}
		}
	}
}


void initTexturesMyDistanceMap(char* distmapName, 
							   char* height_name)
{
	dmap_depth = textureDepth;
	
	int w, h, d;
	int resolution; 
	float* myDispmapLower; float* combinedMap;

	//CALCULATING DISTANCE MAP FROM DANIELSSON'S ALGORITHM
	ShImage heightmap;
	printf("\n Creating distance map from %s ", height_name);
	heightmap.loadPng(height_name);
	w = heightmap.width(); h = heightmap.width();  d = dmap_depth;
	textureWidth = heightmap.width();
	//float* myDispmap = new float[w*h*d];
	DistanceMap map = distance_map_algo(heightmap, d);
	float* myDispmap = init_distance_map_float(map);
	delete [] map.m_data;


	///// DISTANCE MAP LOADED FROM HAVOC3D FILE
	//d = textureDepth;  w= textureLength; h = textureWidth; 
	//float* myDispmap = new float[w*h*d];
	//int dim, num,  m=0;
	//char st[100];
	//sprintf(st, "%s_DistanceMap0.txt", distmapName);
	//double* mimg = loadPoints(st, dim, num);

	//for(int j=0; j< h; j++)
	//	for(int i=0; i< w; i++)
	//		for(int k=0; k< d; k++)
	//		{
	//			myDispmap[k*w*h+ j*w+i] = mimg[m];
	//			m++;
	//		}
	//delete [] mimg;

	/////////////////////////////////////////////////////////////////////////////
	// LINEAR FILTER FOR DISTANCE MAP
	/////////////////////////////////////////////////////////////////////////////
	MAXIMUM_DIMENSION = textureWidth;
	LEVELS = log2f(textureDepth); 

	resolution = pow(2, 0);  
	combinedMap = new float[(w/resolution)*(h/resolution)*(d/resolution)];

	for( int ii=0; ii< (h/resolution); ii++)
		for(int jj=0; jj< (h/resolution); jj++ )
			for( int kk = 0; kk < (d/resolution); kk++)
			{
				combinedMap[kk*(w/resolution)*(w/resolution) + jj*(h/resolution) + ii + 0] 
				=	myDispmap[kk*(w/resolution)*(w/resolution) + jj*(h/resolution) + ii + 0];
			}

	glGenTextures(1, &myDtex);
	glBindTexture(GL_TEXTURE_3D , myDtex);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_REPEAT); 
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_REPEAT);

	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LOD, LEVELS);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_LOD, 0 );
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, LEVELS);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_BASE_LEVEL, 0 );
	glTexImage3D(GL_TEXTURE_3D, 0, 1, w/resolution, h/resolution, d/resolution,
		0, GL_LUMINANCE, GL_FLOAT, combinedMap ); 
	delete [] combinedMap;

	for( int i=1; i<= LEVELS; i++)
	{
		resolution = pow(2, i);
		myDispmapLower = new float[(w/resolution)*(h/resolution)*(d/resolution)];
		combinedMap = new float[(w/resolution)*(h/resolution)*(d/resolution)];

		minMipmapDistanceMap(myDispmap, myDispmapLower, w, h, d, resolution );

		for( int ii=0; ii< (h/resolution); ii++)
			for(int jj=0; jj< (h/resolution); jj++ )
				for( int kk = 0; kk < (d/resolution); kk++)
				{
					combinedMap[kk*(w/resolution)*(w/resolution) + jj*(h/resolution) + ii + 0] 
					= myDispmapLower[kk*(w/resolution)*(w/resolution) + jj*(h/resolution) + ii];
				}
		
	    glTexImage3D(GL_TEXTURE_3D, i, 1, w/resolution, h/resolution, d/resolution, 
			0, GL_LUMINANCE, GL_FLOAT, combinedMap  );

		delete [] myDispmapLower;
		delete [] combinedMap;
	}
	delete [] myDispmap;
}

void initTextures(const char* color_name,
				  const char* height_name,
				  const char* normal_name
				  )
{	

	printf("\nHeight field case");
	///////////////////////////////////////////////////////////////////////////////////////////////////////
	// LOADING HEIGHTMAP
	///////////////////////////////////////////////////////////////////////////////////////////////////////
	printf("\nloading %s height map", height_name);
	ShImage heightmap;
	heightmap.loadPng(height_name);
	glGenTextures(1, &htex);
	glBindTexture(GL_TEXTURE_2D, htex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0 );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0 );
	gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB,
		heightmap.width(), heightmap.height(),
		GL_RGB,
		GL_FLOAT, heightmap.data());


	///////////////////////////////////////////////////////////////////////////////////////////////////////
	// LOADING COLOR MAP
	///////////////////////////////////////////////////////////////////////////////////////////////////////
	printf("\nloading %s color map", color_name);
	ShImage colormap;
	colormap.loadPng(color_name);
	glGenTextures(1, &ctex);
	glBindTexture(GL_TEXTURE_2D, ctex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0 );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0 );
	gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB,
		colormap.width(), colormap.height(),
		GL_RGB,
		GL_FLOAT, colormap.data());

	///////////////////////////////////////////////////////////////////////////////////////////////////////
	// LOADING NORMAL MAP
	///////////////////////////////////////////////////////////////////////////////////////////////////////
	printf("\nloading %s normal map", normal_name);
	ShImage normalmap;
	normalmap.loadPng(normal_name);
	glGenTextures(1, &ntex);
	glBindTexture(GL_TEXTURE_2D, ntex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0 );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0 );
	gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB,
		normalmap.width(), normalmap.height(),
		GL_RGB,
		GL_FLOAT, normalmap.data());

}

void initTexturesNonHeight(const char* color_name,
						   const char* height_name,
						   const char* normal_name,
						   const char* normal_nameY
						   )
{	
	printf("\nNon-Height field case");
	///////////////////////////////////////////////////////////////////////////////////////////////////////
	// LOADING HEIGHTMAP
	///////////////////////////////////////////////////////////////////////////////////////////////////////
	printf("\nloading %s height map", height_name);
	ShImage heightmap;
	heightmap.loadPng(height_name);
	glGenTextures(1, &htex);
	glBindTexture(GL_TEXTURE_2D, htex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0 );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0 );
	gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA,
		heightmap.width(), heightmap.height(),
		GL_RGBA,
		GL_FLOAT, heightmap.data());


	///////////////////////////////////////////////////////////////////////////////////////////////////////
	// LOADING COLOR MAP
	///////////////////////////////////////////////////////////////////////////////////////////////////////
	printf("\nloading %s color map", color_name);
	ShImage colormap;
	colormap.loadPng(color_name);
	glGenTextures(1, &ctex);
	glBindTexture(GL_TEXTURE_2D, ctex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0 );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0 );
	gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB,
		colormap.width(), colormap.height(),
		GL_RGB,
		GL_FLOAT, colormap.data());

	///////////////////////////////////////////////////////////////////////////////////////////////////////
	// LOADING NORMAL MAP
	///////////////////////////////////////////////////////////////////////////////////////////////////////
	printf("\nloading %s normal map ", normal_name);
	ShImage normalmap;
	normalmap.loadPng(normal_name);
	glGenTextures(1, &ntex);
	glBindTexture(GL_TEXTURE_2D, ntex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0 );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0 );
	gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA,
		normalmap.width(), normalmap.height(),
		GL_RGBA,
		GL_FLOAT, normalmap.data());

	///////////////////////////////////////////////////////////////////////////////////////////////////////
	// LOADING NORMAL MAP 2 
	///////////////////////////////////////////////////////////////////////////////////////////////////////
	printf("\nloading %s normal map 2  ", normal_nameY);
	ShImage normalmapY;
	normalmapY.loadPng(normal_nameY);
	glGenTextures(1, &ntexY);
	glBindTexture(GL_TEXTURE_2D, ntexY);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0 );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0 );
	gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA,
		normalmapY.width(), normalmapY.height(),
		GL_RGBA,
		GL_FLOAT, normalmapY.data());
}


void initTexturesMaximumMipmap(const char* maxmipmap_name)
{	
	///////////////////////////////////////////////////////////////////////////////////////////////////////
	// LOADING MAXIMUM MIPMAP HEIGHTMAP
	///////////////////////////////////////////////////////////////////////////////////////////////////////
	char st[100];
	sprintf(st, "%s.png", maxmipmap_name);
	printf("\nloading %s Maximum mipmap", st);

	ShImage Maxmipmap;
	Maxmipmap.loadPng(st);
	int LEVELS = log2f(Maxmipmap.width());

	printf("\n LEVELS %d", LEVELS);
	glGenTextures(1, &mmtex);
	glBindTexture(GL_TEXTURE_2D, mmtex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, LEVELS );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0 );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, LEVELS );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0 );

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, Maxmipmap.width(), Maxmipmap.height(), 
		0, GL_RGB, GL_FLOAT, Maxmipmap.data());
	Maxmipmap.releaseRef();

	for(int i = 1; i<=LEVELS; i++)
	{
		int size = pow((float)2.0, (float)(LEVELS-i));

		sprintf(st, "%s_%d.png", maxmipmap_name, size );
		printf("\nloading %s Maximum mipmap ", st);

		ShImage Maxmipmap_Temp;
		Maxmipmap_Temp.loadPng(st);
		glTexImage2D(GL_TEXTURE_2D, i, GL_RGB,Maxmipmap_Temp.width(), Maxmipmap_Temp.height(), 
			0, GL_RGB, GL_FLOAT, Maxmipmap_Temp.data());
		Maxmipmap_Temp.releaseRef();
	}
}

void initCgContextProfileVertexProgram()
{
	myCgContext = cgCreateContext();
	checkForCgError("creating context");
	cgGLSetDebugMode( CG_FALSE );

	myCgVertexProfile = cgGLGetLatestProfile(CG_GL_VERTEX);
	cgGLSetOptimalOptions(myCgVertexProfile);
	checkForCgError("selecting vertex profile");

	////////////////////   VERTEX PROGRAM ///////////////////////////////
	myCgVertexProgram =
		cgCreateProgramFromFile(
		myCgContext,              /* Cg runtime context */
		CG_SOURCE,                /* Program in human-readable form */
		"Vertex.cg",  /* Name of file containing program */
		myCgVertexProfile,        /* Profile: OpenGL ARB vertex program */
		"Movement",      /* Entry function name */
		NULL);                    /* No extra commyPiler options */
	checkForCgError("creating vertex program from file");
	cgGLLoadProgram(myCgVertexProgram);
	checkForCgError("loading vertex program");

	// modelview projection
	myCgModelViewProjection = cgGetNamedParameter( myCgVertexProgram,"modelViewProj");
	checkForCgError("Getting modelviewProj matrix parameter");

	// eye coordinate
	myCgEyeCoordinate = cgGetNamedParameter( myCgVertexProgram,"eyeCoord");
	checkForCgError("Getting eyeCoord matrix parameter");

	// light coordinate
	myCgLightCoordinate = cgGetNamedParameter( myCgVertexProgram,"lightCoord");
	checkForCgError("Getting lightCoord matrix parameter");

	// inv bump depth
	myCgInvBumpDepth = cgGetNamedParameter( myCgVertexProgram,"invBumpDepth");
	checkForCgError("Getting invBumpDepth matrix parameter");

	cgGLSetParameter1f( myCgInvBumpDepth, float(1.0)/bumpdepth );
	checkForCgError("Setting invBumpDepth matrix parameter");

}

void initCgFragmentProgramSetup(int which)
{
	myCgFragmentProfile = cgGLGetLatestProfile(CG_GL_FRAGMENT);

	cgGLSetOptimalOptions(myCgFragmentProfile);
	checkForCgError("selecting fragment profile");

	if(which == 0 || which == -1)
	{
		myCgFragmentProgram1 =
			cgCreateProgramFromFile(
			myCgContext,              /* Cg runtime context */
			CG_SOURCE,                /* Program in human-readable form */
			"Fragment_SphereTracing.cg",
			myCgFragmentProfile,      /* Profile: latest fragment profile */
			"Lighting",    /* Entry function name */
			NULL); /* No extra commyPiler options */
	}

	if(which == 1 || which == -1 )
	{
		myCgFragmentProgram2 =
			cgCreateProgramFromFile(
			myCgContext,              /* Cg runtime context */
			CG_SOURCE,                /* Program in human-readable form */
			"Fragment_ReliefMap.cg",
			myCgFragmentProfile,      /* Profile: latest fragment profile */
			"Lighting",    /* Entry function name */
			NULL); /* No extra commyPiler options */
	}

	if(which == 2 || which == -1 )
	{
		myCgFragmentProgram3 =
			cgCreateProgramFromFile(
			myCgContext,              /* Cg runtime context */
			CG_SOURCE,                /* Program in human-readable form */
			"Fragment_NonHeightfield_ReliefMap.cg",
			myCgFragmentProfile,      /* Profile: latest fragment profile */
			"Lighting",    /* Entry function name */
			NULL); /* No extra commyPiler options */
	}

	if(which == 3 || which == -1 )
	{
		myCgFragmentProgram4 =
			cgCreateProgramFromFile(
			myCgContext,              /* Cg runtime context */
			CG_SOURCE,                /* Program in human-readable form */
			"Fragment_Pyramidal.cg",
			myCgFragmentProfile,      /* Profile: latest fragment profile */
			"Lighting",    /* Entry function name */
			NULL); /* No extra commyPiler options */
	}

}

void initCgFragmentShaders(int which)
{
	////////////////////   FRAGMENT PROGRAM ///////////////////////////////
	whichFragment = which;
	myCgFragmentProfile = cgGLGetLatestProfile(CG_GL_FRAGMENT);
	//myCgFragmentProfile = cgGetProfile("fp40");

	cgGLSetOptimalOptions(myCgFragmentProfile);
	checkForCgError("selecting fragment profile");
	//const char* name = cgGetProfileString(myCgFragmentProfile);
	//printf(" \n Profile name %s " , name );

	switch(which)
	{
	case 0:
		myCgFragmentProgram =	myCgFragmentProgram1;
		break;
	case 1:
		myCgFragmentProgram =	myCgFragmentProgram2;
		break;
	case 2:
		myCgFragmentProgram =	myCgFragmentProgram3;

		if(NON_HEIGHT_FIELD)
		{	// normal texture
			myCgNormalTextureY = cgGetNamedParameter( myCgFragmentProgram, "normalTexY");
			checkForCgError("loading normalTex from file");

			cgGLSetTextureParameter( myCgNormalTextureY, ntexY);
			checkForCgError("loading normal texture in program");
		}
		break;

	case 3:
		myCgFragmentProgram =	myCgFragmentProgram4;

		myCgMipMapTex = cgGetNamedParameter( myCgFragmentProgram, "mipMapTex");
		checkForCgError("loading myCgMipMapTex from file");

		cgGLSetTextureParameter( myCgMipMapTex, mmtex);
		checkForCgError("loading myCgMipMapTex texture in program");

		break;
	}
	cgGLLoadProgram(myCgFragmentProgram);
	checkForCgError("loading fragment programs");

	// color texture
	myCgColorTexture = cgGetNamedParameter( myCgFragmentProgram, "colorTex");
	checkForCgError("loading colorTex1 from file");

	cgGLSetTextureParameter( myCgColorTexture, ctex);
	checkForCgError("loading color1 texture in program");

	// normal texture
	myCgNormalTexture = cgGetNamedParameter( myCgFragmentProgram, "normalTex");
	checkForCgError("loading normalTex from file");

	cgGLSetTextureParameter( myCgNormalTexture, ntex);
	checkForCgError("loading normal texture in program");

	// normalization factor
	myCgNormalizationFactor = cgGetNamedParameter( myCgFragmentProgram, "normalizationFactor");
	checkForCgError("loading normalizationFactor from file");

	cgGLSetParameter1f( myCgNormalizationFactor, ((double)textureDepth)/textureWidth ); 
	checkForCgError("loading myCgNormalizationFactor from file");

	// NUM_ITERATIONS
	myCgNumIterations = cgGetNamedParameter( myCgFragmentProgram, "NUM_ITERATIONS");
	checkForCgError("loading NUM_ITERATIONS from file");

	cgGLSetParameter1d( myCgNumIterations, iterations ); 
	checkForCgError("loading NUM_ITERATIONS from file");

	// keypress c
	myCgKeyPressed_c = cgGetNamedParameter( myCgFragmentProgram, "keyPress_c");
	checkForCgError("loading keyPress_c from file");

	cgGLSetParameter1f( myCgKeyPressed_c, keyPressed_c ); 
	checkForCgError("loading NUM_ITERATIONS from file");

	// second color texture
	myCgHeightTexture = cgGetNamedParameter( myCgFragmentProgram, "heightTex");
	checkForCgError("loading colorTex2 from file");

	cgGLSetTextureParameter( myCgHeightTexture, htex);
	checkForCgError("loading color2 texture in program");

	// distance texture 5
	myCgDistanceTexture= cgGetNamedParameter( myCgFragmentProgram, "distanceTex");
	checkForCgError("loading distanceTex from file");

	cgGLSetTextureParameter( myCgDistanceTexture, myDtex);
	checkForCgError("loading distance texture in program");

	// keypressed_d
	myCgParametricIterations = cgGetNamedParameter( myCgFragmentProgram, "keyPress_d");
	checkForCgError("loading normalizationFactor from file");

	cgGLSetParameter1f( myCgParametricIterations, keyPressed_d); 
	checkForCgError("loading myCgNormalizationFactor from file");

	// max dimension
	myCgMaximumResolution = cgGetNamedParameter( myCgFragmentProgram, "MAXIMUM_DIMENSION");
	checkForCgError("loading MAXIMUM_DIMENSION from file");

	cgGLSetParameter1f( myCgMaximumResolution, MAXIMUM_DIMENSION); 
	checkForCgError("loading myCgNormalizationFactor from file");

	// non-height field rendering parameters
	myCgXrepeat = cgGetNamedParameter( myCgFragmentProgram, "xrepeat");
	checkForCgError("loading xrepeat from file");

	myCgYrepeat = cgGetNamedParameter( myCgFragmentProgram, "yrepeat");
	checkForCgError("loading yrepeat from file");

	myCgZrepeat = cgGetNamedParameter( myCgFragmentProgram, "zrepeat");
	checkForCgError("loading zrepeat from file");

	myZreject = cgGetNamedParameter( myCgFragmentProgram, "zReject");
	checkForCgError("loading zReject from file");

	cgGLSetParameter1d( myCgXrepeat, 1); 
	checkForCgError("loading myCgXrepeat from file");

	cgGLSetParameter1d( myCgYrepeat, 1); 
	checkForCgError("loading myCgYrepeat from file");

	cgGLSetParameter1d( myCgZrepeat, 1); 
	checkForCgError("loading myCgZrepeat from file");

	cgGLSetParameter1f( myZreject, 0.7); 
	checkForCgError("loading myZreject from file");

}	
void cgSetUpView()
{
	buildLookAtMatrix(eyePosition[0], eyePosition[1], eyePosition[2],
		eyePosition[0], eyePosition[1]-0.2, eyePosition[2]-1,
		0, 1, 0,
		viewMatrix);

	float tempRotateMatrix1[16], tempRotateMatrix2[16];
	float objSpaceEyePos[4], objSpaceLightPos[4];

	lightPosition[0] = 5*cos((M_PI/180)*light_phi)*sin((M_PI/180)*light_theta); 
	lightPosition[1] = 5*cos((M_PI/180)*light_theta);
	lightPosition[2] = 5*sin((M_PI/180)*light_phi)*sin((M_PI/180)*light_theta);
	lightPosition[3] = 1; 

	makeRotateMatrix( phi, 0, 0, 1, tempRotateMatrix1);
	makeRotateMatrix( theta, 1, 0, 0, tempRotateMatrix2);
	multMatrix( rotateMatrix, tempRotateMatrix2, tempRotateMatrix1 ); // assuming src1*src2

	makeTranslateMatrix(objectPosition[0], objectPosition[1], objectPosition[2], translateMatrix);
	multMatrix(modelMatrix,  translateMatrix, rotateMatrix);
	invertMatrix(invModelMatrix, modelMatrix );

	multMatrix(modelViewMatrix, viewMatrix, modelMatrix);
	multMatrix(modelViewProjMatrix, myProjectionMatrix, modelViewMatrix);

	cgSetMatrixParameterfr(myCgModelViewProjection, modelViewProjMatrix);
	checkForCgError("loading modelviewprojection matrix");

	transform( objSpaceEyePos, invModelMatrix, eyePosition ); 
	cgSetParameter3f( myCgEyeCoordinate, objSpaceEyePos[0], objSpaceEyePos[1], objSpaceEyePos[2] );
	//printf("Eyeposition  %f %f %f", objSpaceEyePos[0], objSpaceEyePos[1], objSpaceEyePos[2]);

	transform( objSpaceLightPos, invModelMatrix, lightPosition ); 
	cgSetParameter3f( myCgLightCoordinate, objSpaceLightPos[0], objSpaceLightPos[1], objSpaceLightPos[2] );
	//cgSetParameter3f( myCgLightCoordinate, lightPosition[0], lightPosition[1], lightPosition[2] );
}


static void cgReshape(int width, int height)
{
	double aspectRatio = (float) width / (float) height;
	double fieldOfView = 60.0; /* Degrees */

	buildPerspectiveMatrix(fieldOfView, aspectRatio,
		0.1, 10000.0, 
		myProjectionMatrix);
	glViewport(0, 0, width, height);

}

int tangentSpace(float* c1, float* c2, float* c3, float* v1, float* v2, float* v3, 
				 float* n1, float* n2, float* n3, float* t1, float* b1, float* nn1)
{
	//if( c1 == NULL || c2 == NULL || c3 == NULL ||
	//	v1 == NULL || v2 == NULL || v3 == NULL ||
	//	n1 == NULL || n2 == NULL || n3 == NULL )
	//	{
	//	glMultiTexCoord3fARB(GL_TEXTURE0 + 1, 1.0, 0.0, 0.0); // tangents
	//	glMultiTexCoord3fARB(GL_TEXTURE0 + 2, 0.0, 1.0, 0.0); // binormals
	//	glNormal3f(0.0, 0.0, 1.0 );

	//	t1[0] = 1.0; t1[1] = 0.0; t1[2] = 0.0; 
	//	b1[0] = 0.0; b1[1] = 1.0; b1[2] = 0.0; 
	//	nn1[0] = 0.0; nn1[1] = 0.0; nn1[2] = 1.0;
	//	return -1;
	// }
	double Mtex = ( c2[0]-c1[0] )*( c3[1]-c1[1] )  - ( c3[0]-c1[0] )*( c2[1]-c1[1]);

	if(abs(Mtex) <= 0.00 )
	{
		glMultiTexCoord3fARB(GL_TEXTURE0 + 1, 1.0, 0.0, 0.0); // tangents
		glMultiTexCoord3fARB(GL_TEXTURE0 + 2, 0.0, 1.0, 0.0); // binormals
		glNormal3f(0.0, 0.0, 1.0 );

		t1[0] = 1.0; t1[1] = 0.0; t1[2] = 0.0; 
		b1[0] = 0.0; b1[1] = 1.0; b1[2] = 0.0; 
		nn1[0] = 0.0; nn1[1] = 0.0; nn1[2] = 1.0;
		//printf("%f Mtex becoming zero ", Mtex);
		return -1;
	}

	double T[3] = {
		( c3[1]-c1[1] )*( v2[0] - v1[0] ) - ( c2[1]-c1[1] )*( v3[0] - v1[0] ),
			( c3[1]-c1[1] )*( v2[1] - v1[1] ) - ( c2[1]-c1[1] )*( v3[1] - v1[1] ),
			( c3[1]-c1[1] )*( v2[2] - v1[2] ) - ( c2[1]-c1[1] )*( v3[2] - v1[2] )
	};
	double Tmag = magnitude(T);
	T[0] = T[0]/Tmag;  T[1] = T[1]/Tmag;  T[2] = T[2]/Tmag;

	double B[3] = {
		-( c3[0]-c1[0] )*( v2[0] - v1[0] ) + ( c2[0]-c1[0] )*( v3[0] - v1[0] ),
			-( c3[0]-c1[0] )*( v2[1] - v1[1] ) + ( c2[0]-c1[0] )*( v3[1] - v1[1] ),
			-( c3[0]-c1[0] )*( v2[2] - v1[2] ) + ( c2[0]-c1[0] )*( v3[2] - v1[2] )
	};
	double Bmag = magnitude(T);// not same normalization
	B[0] = B[0]/Bmag;  B[1] = B[1]/Bmag;  B[2] = B[2]/Bmag;

	double N[3], n1mag = magnitude(n1);

	//if( dotProduct(T, B) == 0 )
	{
		crossProduct(T, B, N);
		//N[0] = n1[0]/n1mag; N[1] = n1[1]/n1mag; N[2] = n1[2]/n1mag;

		//printf("orthogonal tangent and binormal \n");

		double E[3];
		crossProduct( T, B, E );

		if( dotProduct(E, N) < 0 )
		{
			B[0] = -B[0]; B[1] = -B[1]; B[2] = -B[2];
			crossProduct( N, T, B );
			printf("\n\nWrong binormal");
		}

		glMultiTexCoord3fARB(GL_TEXTURE0 + 1, T[0], T[1], T[2]); // tangents
		glMultiTexCoord3fARB(GL_TEXTURE0 + 2, B[0], B[1], B[2]); // binormals
		glNormal3f(N[0], N[1], N[2] );							 //normals

		t1[0] = T[0]; t1[1] = T[1]; t1[2] = T[2]; 
		b1[0] = B[0]; b1[1] = B[1]; b1[2] = B[2]; 
		nn1[0] = N[0]; nn1[1] = N[1]; nn1[2] = N[2];
	}
	//else
	//{
	//N[0] = n1[0]/n1mag; N[1] = n1[1]/n1mag; N[2] = n1[2]/n1mag;
	////printf("Non-orthogonal tangent and binormal \n");

	//double BN[3], NT[3], TB[3]; 

	//crossProduct( B, N, BN );	BN[1]= -BN[1];
	//crossProduct( N, T, NT );	NT[0]= -NT[0];	NT[2]= -NT[2];
	//crossProduct( T, B, TB );	TB[1] = -TB[1];

	//glMultiTexCoord3fARB(GL_TEXTURE0 + 1, BN[0], BN[1], BN[2]); // tangents
	//glMultiTexCoord3fARB(GL_TEXTURE0 + 2, NT[0], NT[1], NT[2]); // binormals
	//glNormal3f(TB[0], TB[1], TB[2] ); //normals
	//
	//t1[0] = BN[0]; t1[1] = BN[1]; t1[2] = BN[2]; 
	//b1[0] = NT[0]; b1[1] = NT[1]; b1[2] = NT[2]; 
	//nn1[0] = TB[0]; nn1[1] = TB[1]; nn1[2] = TB[2];
	//}
	//
	return 0;
}

void modelSetUp(GLMmodel* model)
{
	printf("\nStarting model setup");
	static GLuint i;
	static GLMgroup* group;
	static GLMtriangle* triangle;

	if (model->TBNtangents)
		free(model->TBNtangents);

	if (model->TBNbinormals)
		free(model->TBNbinormals);

	if (model->TBNnormals)
		free(model->TBNnormals);

	group = model->groups;

	model->numTBNtangents = model->numnormals;
	model->numTBNbinormals = model->numnormals;
	model->numTBNnormals = model->numnormals;

	model->TBNtangents = (float*)malloc(sizeof(float)* 3* (model->numnormals+1));
	model->TBNbinormals = (float*)malloc(sizeof(float)* 3* (model->numnormals+1));
	model->TBNnormals = (float*)malloc(sizeof(float)* 3* (model->numnormals+1));

	while(group) {

		glBegin(GL_TRIANGLES);
		for (i = 0; i < group->numtriangles; i++) {
			triangle = &T(group->triangles[i]);

			float  *c1 = NULL,		   *c2 = NULL,		   *c3 = NULL,
				*v1 = NULL,		   *v2 = NULL,		   *v3 = NULL,
				*n1 = NULL,		   *n2 = NULL,		   *n3 = NULL,

				*t1 = NULL,		   *t2 = NULL,		   *t3 = NULL,
				*b1 = NULL,		   *b2 = NULL,		   *b3 = NULL,
				*nn1 = NULL,		   *nn2 = NULL,		   *nn3 = NULL;

			c1 = &model->texcoords[2 * triangle->tindices[0]];
			c2 = &model->texcoords[2 * triangle->tindices[1]];
			c3 = &model->texcoords[2 * triangle->tindices[2]];

			v1 = &model->vertices[3 * triangle->vindices[0]];
			v2 = &model->vertices[3 * triangle->vindices[1]];
			v3 = &model->vertices[3 * triangle->vindices[2]];

			n1 = &model->normals[3 * triangle->nindices[0]];
			n2 = &model->normals[3 * triangle->nindices[1]];
			n3 = &model->normals[3 * triangle->nindices[2]];


			t1 = &model->TBNtangents[3 * triangle->nindices[0]];
			t2 = &model->TBNtangents[3 * triangle->nindices[1]];
			t3 = &model->TBNtangents[3 * triangle->nindices[2]];

			b1 = &model->TBNbinormals[3 * triangle->nindices[0]];
			b2 = &model->TBNbinormals[3 * triangle->nindices[1]];
			b3 = &model->TBNbinormals[3 * triangle->nindices[2]];

			nn1 = &model->TBNnormals[3 * triangle->nindices[0]];
			nn2 = &model->TBNnormals[3 * triangle->nindices[1]];
			nn3 = &model->TBNnormals[3 * triangle->nindices[2]];


			if( tangentSpace(c1, c2, c3, v1, v2, v3, n1, n2, n3, t1, b1, nn1) == -1)
			{
				//printf(" %d  out of  %d \n", i, group->numtriangles);
				//printf(" tangent space 1 \n");
			}


			glTexCoord2fv(c1);
			glVertex3fv(v1);

			if( tangentSpace(c2, c3, c1, v2, v3, v1, n2, n3, n1, t2, b2, nn2 ) == -1) 
			{
				//printf(" %d  out of  %d \n", i, group->numtriangles);
				//printf(" tangent space 2 \n");
			}
			glTexCoord2fv(c2);
			glVertex3fv(v2);


			if( tangentSpace(c3, c1, c2, v3, v1, v2, n3, n1, n2, t3, b3, nn3 ) == -1)
			{
				//printf(" %d  out of  %d \n", i, group->numtriangles);
				//printf(" tangent space 3 \n");

			}
			glTexCoord2fv(c3);
			glVertex3fv(v3);

		}
		glEnd();

		group = group->next;
	}
	printf("Model setup complete");
}

void modelDraw(GLMmodel* model)
{
	static GLuint i;
	static GLMgroup* group;
	static GLMtriangle* triangle;

	group = model->groups;

	while(group) {

		glBegin(GL_TRIANGLES);
		for (i = 0; i < group->numtriangles; i++) {
			triangle = &T(group->triangles[i]);

			float  *c1 = &model->texcoords[2 * triangle->tindices[0]],
				*c2 = &model->texcoords[2 * triangle->tindices[1]],
				*c3 = &model->texcoords[2 * triangle->tindices[2]],

				*v1 = &model->vertices[3 * triangle->vindices[0]],
				*v2 = &model->vertices[3 * triangle->vindices[1]],
				*v3 = &model->vertices[3 * triangle->vindices[2]],

				*t1 = &model->TBNtangents[3 * triangle->nindices[0]],
				*t2 = &model->TBNtangents[3 * triangle->nindices[1]],
				*t3 = &model->TBNtangents[3 * triangle->nindices[2]],

				*b1 = &model->TBNbinormals[3 * triangle->nindices[0]],
				*b2 = &model->TBNbinormals[3 * triangle->nindices[1]],
				*b3 = &model->TBNbinormals[3 * triangle->nindices[2]],

				*nn1 = &model->TBNnormals[3 * triangle->nindices[0]],
				*nn2 = &model->TBNnormals[3 * triangle->nindices[1]],
				*nn3 = &model->TBNnormals[3 * triangle->nindices[2]]
				;


				glMultiTexCoord3fARB(GL_TEXTURE0 + 1, t1[0], t1[1], t1[2]); // tangents
				glMultiTexCoord3fARB(GL_TEXTURE0 + 2, b1[0], b1[1], b1[2]); // binormals
				glNormal3f(nn1[0], nn1[1], nn1[2] );
				glTexCoord2fv(c1);
				glVertex3fv(v1);

				glMultiTexCoord3fARB(GL_TEXTURE0 + 1, t2[0], t2[1], t2[2]); // tangents
				glMultiTexCoord3fARB(GL_TEXTURE0 + 2, b2[0], b2[1], b2[2]); // binormals
				glNormal3f(nn2[0], nn2[1], nn2[2] );
				glTexCoord2fv(c2);
				glVertex3fv(v2);

				glMultiTexCoord3fARB(GL_TEXTURE0 + 1, t3[0], t3[1], t3[2]); // tangents
				glMultiTexCoord3fARB(GL_TEXTURE0 + 2, b3[0], b3[1], b3[2]); // binormals

				glNormal3f(nn3[0], nn3[1], nn3[2] );
				glTexCoord2fv(c3);
				glVertex3fv(v3);

		}
		glEnd();

		group = group->next;
	}
}

void resetView()
{
	phi+= 0.2;
	float tempRotateMatrix1[16], tempRotateMatrix2[16];
	float objSpaceEyePos[4], objSpaceLightPos[4];

	makeRotateMatrix( phi, 0, 1, 0, tempRotateMatrix1);
	makeRotateMatrix( theta, 1, 0, 0, tempRotateMatrix2);
	multMatrix( rotateMatrix, tempRotateMatrix2, tempRotateMatrix1 ); // assuming src1*src2

	multMatrix(modelMatrix,  translateMatrix, rotateMatrix);
	invertMatrix( invModelMatrix, modelMatrix );

	multMatrix(modelViewMatrix, viewMatrix, modelMatrix);
	multMatrix(modelViewProjMatrix, myProjectionMatrix, modelViewMatrix);

	// setting cg parameters
	cgSetMatrixParameterfr(myCgModelViewProjection, modelViewProjMatrix);
	checkForCgError("loading modelviewprojection matrix");

	transform( objSpaceEyePos, invModelMatrix, eyePosition ); 
	cgSetParameter3f( myCgEyeCoordinate, objSpaceEyePos[0], objSpaceEyePos[1], objSpaceEyePos[2] );

	transform( objSpaceLightPos, invModelMatrix, lightPosition ); 
	cgSetParameter3f( myCgLightCoordinate, objSpaceLightPos[0], objSpaceLightPos[1], objSpaceLightPos[2] );

}

void display()
{
	cgGLBindProgram(myCgVertexProgram);
	checkForCgError("binding vertex program");

	cgGLBindProgram(myCgFragmentProgram);
	checkForCgError("binding fragment program");

	cgGLEnableProfile(myCgVertexProfile);
	checkForCgError("enabling vertex profile");

	cgGLEnableProfile(myCgFragmentProfile);
	checkForCgError("enabling fragment profile");

	cgGLEnableTextureParameter(myCgColorTexture);
	checkForCgError("enable color texture");

	cgGLEnableTextureParameter(myCgNormalTexture);
	checkForCgError("enable normal texture");

	cgGLEnableTextureParameter(myCgHeightTexture);
	checkForCgError("enable color texture2");

	cgGLEnableTextureParameter(myCgDistanceTexture);
	checkForCgError("enable distance texture");

	if(NON_HEIGHT_FIELD && whichFragment == 2)
	{
		cgGLEnableTextureParameter(myCgNormalTextureY);
		checkForCgError("enable normalY texture");
	}

	if(whichFragment == 3)
	{
		cgGLEnableTextureParameter(myCgMipMapTex);
		checkForCgError("enable myCgMipMapTex texture");
	}


	float factor = 400.0;
	drawCuboid( 0.0, 0.0, factor, factor );

   
	cgGLDisableProfile(myCgVertexProfile);
	checkForCgError("enabling vertex profile");

	cgGLDisableProfile(myCgFragmentProfile);
	checkForCgError("enabling fragment profile");

	cgGLDisableTextureParameter(myCgColorTexture);
	checkForCgError("Disable color texture");

	cgGLDisableTextureParameter(myCgNormalTexture);
	checkForCgError("Disable normal texture");

	cgGLDisableTextureParameter(myCgHeightTexture);
	checkForCgError("Disable color texture2");

	cgGLDisableTextureParameter(myCgDistanceTexture);
	checkForCgError("Disable distance texture");

	if(NON_HEIGHT_FIELD && whichFragment == 2)
	{	
		cgGLDisableTextureParameter(myCgNormalTextureY);
		checkForCgError("Disable normal texture");
	}
	if(whichFragment == 3)
	{
		cgGLDisableTextureParameter(myCgMipMapTex);
		checkForCgError("Disable myCgMipMapTex texture");
	}
}


void mainDisplay()
{
	char fragProg[100]="", title[200]="";

	glFrontFace(GL_CW);
	glEnable(GL_CULL_FACE);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	display();


	switch(whichFragment)
	{
	case 0:
		sprintf(fragProg, "%s", "Sphere tracing ");
		break;
	case 1:
		sprintf(fragProg, "%s", "Relief mapping Height field ");
		break;
	case 2:
		sprintf(fragProg, "%s", "Relief mapping Non-height field ");
		break;
	case 3:
		sprintf(fragProg, "%s", "Pyramidal mapping ");
		break;
	}

	sprintf( title, "%s %3.2f Depth  %d Iterations    %3.2f key1    %3.2f  key2  %3.2f", fragProg,
		bumpdepth, iterations, keyPressed, keyPressed_c,  keyPressed_d);

	glutSetWindowTitle(title);
	glutSwapBuffers();
}

void motion(int x, int y)
{
	const double factor = 20.0;

	if (buttons[GLUT_LEFT_BUTTON] == GLUT_DOWN)
	{
		if(move_light) 
		{
			light_theta += (y - cur_y)/5;
			light_phi += (x - cur_x)/5;
			if(light_theta > 90) light_theta = 90;
			if(light_theta < 10) light_theta = 10;

		}
		else 
		{
			theta -= (y - cur_y)/5;
			phi -= (x - cur_x)/5;
			/*  if(theta > 90) theta = 90;
			if(theta < -10) theta = 10;*/
		}
		changed = true;
	}

	if (buttons[GLUT_MIDDLE_BUTTON] == GLUT_DOWN) 
	{
		objectPosition[0]+= 0;
		objectPosition[1]+= 0;
		objectPosition[2]-= (y - cur_y)/factor*10;
		changed = true;
	}

	if (buttons[GLUT_RIGHT_BUTTON] == GLUT_DOWN) 
	{
		objectPosition[0]-= (cur_x - x)/factor * 10;
		objectPosition[1]+= (cur_y - y)/factor * 10;
		objectPosition[2]+= 0;
		changed = true;
	}

	cur_x = x;
	cur_y = y;
}

void mouse(int button, int state, int x, int y)
{
	buttons[button] = state;
	cur_x = x;
	cur_y = y;
}

void keyboard(unsigned char key, int x, int y) {

	if (key == '-') 
	{
		bumpdepth -= 0.05;

		if (bumpdepth < 0.0) 
			bumpdepth = 0.0;

		cgGLSetParameter1f( myCgInvBumpDepth, float(1.0)/bumpdepth );
		checkForCgError("Setting invBumpDepth matrix parameter");
		glutPostRedisplay();	
	}
	if (key == '+')
	{
		bumpdepth += 0.05;
		cgGLSetParameter1f( myCgInvBumpDepth, float(1.0)/bumpdepth );
		checkForCgError("Setting invBumpDepth matrix parameter");
		glutPostRedisplay();
	}
	if (key == 'I') 
	{
		iterations += 2;
		cgGLSetParameter1d( myCgNumIterations,  iterations);
		checkForCgError("Setting NUM_ITERATIONS matrix parameter");
		glutPostRedisplay();
	}
	if (key == 'i') 
	{
		iterations -= 2;

		if (iterations < 0) 
			iterations = 0;

		cgGLSetParameter1d( myCgNumIterations,  iterations);
		checkForCgError("Setting NUM_ITERATIONS matrix parameter");
		glutPostRedisplay();

	}

	if(tolower(key) == 'l') 
	{
		move_light = !move_light;
		glutPostRedisplay();
	}

	if(key == '1')
	{
		keyPressed++;

		if(keyPressed <= 0)
		{
			initCgFragmentProgramSetup(0);
			initCgFragmentShaders(0);
		}
		else 
		{
			initCgFragmentProgramSetup(floor(keyPressed));
			initCgFragmentShaders(floor(keyPressed));
		}
		cgGLSetParameter1d( myCgNumIterations,  iterations);
		checkForCgError("Setting iterations  parameter");
		glutPostRedisplay();
	}

	if(key == '2')
	{
		keyPressed--;

		if(keyPressed <= 0)
		{
			initCgFragmentProgramSetup(0);
			initCgFragmentShaders(0);
		}
		else 
		{
			initCgFragmentProgramSetup(floor(keyPressed));
			initCgFragmentShaders(floor(keyPressed));
		}
		cgGLSetParameter1d( myCgNumIterations,  iterations);
		checkForCgError("Setting iterations  parameter");
		glutPostRedisplay();
	}


	if(key == '5')
	{
		keyPressed_c+= 1;
		cgGLSetParameter1f( myCgKeyPressed_c, keyPressed_c ); 
		checkForCgError("loading NUM_ITERATIONS from file");
		glutPostRedisplay();

	}

	if(key == '6')
	{
		keyPressed_c-= 1;
		cgGLSetParameter1f( myCgKeyPressed_c, keyPressed_c ); 
		checkForCgError("loading NUM_ITERATIONS from file");
		glutPostRedisplay();
	}

	if(key == '7')
	{
		tileResolution++;
		glutPostRedisplay();
	}

	if(key == '8')
	{
		if(tileResolution >= 2)
			tileResolution--;

		glutPostRedisplay();
	}

	if(key == 'r' || key=='R')
		sceneRotate = !sceneRotate;

	if(key == '9' )
	{
		keyPressed_d+=1;
		cgGLSetParameter1f( myCgParametricIterations, keyPressed_d); 
		checkForCgError("loading myCgNormalizationFactor from file");
		glutPostRedisplay();
	}

	if(key == '0' )
	{
		keyPressed_d-=1;
		cgGLSetParameter1f( myCgParametricIterations, keyPressed_d); 
		checkForCgError("loading myCgNormalizationFactor from file");
		glutPostRedisplay();
	}
}

void idle() {  

	if (changed) 
	{
		cgSetUpView();
		changed = false;
	}
	if(sceneRotate)
		resetView();

	glutPostRedisplay();
}

int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutInitWindowSize(1024, 768);
	glutCreateWindow("Displacement Mapping in Pixel domain");
	glutDisplayFunc(mainDisplay);

	glutReshapeFunc(cgReshape);
	glutMouseFunc(mouse);
	glutMotionFunc(motion);
	glutKeyboardFunc(keyboard);  
	glutIdleFunc(idle);
	initExtensions();

	if(argc >= 6 && argc <= 8)
	{
		int resolution = atoi(argv[1]);


		switch( resolution)
		{
		case 128  :
			textureDepth = 32;				textureLength = 128;				textureWidth = 128;
			break;
		case 12864 :
			textureDepth = 64;				textureLength = 128;				textureWidth = 128;
		case 64 :
			textureDepth = 32;				textureLength = 64;				textureWidth = 64;
			break;
		case 6416 :
			textureDepth = 16;				textureLength = 64;				textureWidth = 64;
			break;
		case 256 :
			textureDepth = 32;				textureLength = 256;				textureWidth = 256;
			break;
		case 25664 :
			textureDepth = 64;				textureLength = 256;				textureWidth = 256;
			break;
		case 51264 :
			textureDepth = 64;				textureLength = 512;				textureWidth = 512;
			break;
		case 512 :
			textureDepth = 32;				textureLength = 512;				textureWidth = 512;
			break;
		case 3204 :
			textureDepth = 4;				textureLength = 32;				textureWidth = 32;
			break;
		}

		initTexturesMyDistanceMap(argv[3], argv[4]);

		if(argc == 7)
		{
			NON_HEIGHT_FIELD = true;
			initTexturesNonHeight(argv[2], argv[4], argv[5], argv[6]);
		}
		else
		{
			initTextures(argv[2], argv[4], argv[5]);

			if(argc == 8 )
				initTexturesMaximumMipmap(argv[6]);
		}
	}
	else
	{
		exit(1);
	}

	glClearColor(0.0, 0.0, 0.0, 1.0);
	glEnable(GL_DEPTH_TEST);

	// CG
	initCgContextProfileVertexProgram();
	initCgFragmentProgramSetup(-1);
	initCgFragmentShaders(keyPressed);
	cgSetUpView();

	//#ifdef MODEL3D
	//	model1 = glmReadOBJ("Models\\scene_sphere.obj");
	//	glmScale(model2,400);
	//	modelSetUp(model1);
	//#endif

	glutMainLoop();

}
